Best Practices for Developing with TypeScript in 2023
https://medium.com/@worachote/best-practices-for-developing-with-typescript-in-2023-de88fc6f96a0
インターフェース分離の原則
インターフェースを使う者が不要な依存関係に縛られず、必要な機能のインターフェースのみに依存するべき。
できるだけ小さなインターフェースにすることで、コード再利用性が向上し、影響範囲が小さくできる。
code:ts
// NG
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
const developer: Worker = {
work: () => console.log("Coding..."),
eat: () => console.log("Eating..."),
sleep: () => console.log("Sleeping..."),
};
const robot: Worker = {
work: () => console.log("Working..."),
// Robotには eat や sleep は不要なのに、定義を強制される
eat: () => { throw new Error("Robots don't eat!"); },
sleep: () => { throw new Error("Robots don't sleep!"); },
};
// OK
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
const developer: Workable & Eatable & Sleepable = {
work: () => console.log("Coding..."),
eat: () => console.log("Eating..."),
sleep: () => console.log("Sleeping..."),
};
const robot: Workable = {
work: () => console.log("Working..."),
};
継承と合成(インターセクション)の使い分け
<extends>
型の一貫性を保ちたい場合に使う。
以下のように型を意図せず更新されない(プロパティの競合が発生するなら安全性の観点でこちら)。
code:ts
interface A {
value: number;
}
interface B extends A {
value: string; // エラー!
}
<& (intersection)>
上と同様に型の統合ができるが、同じ型がある場合、never型になるので安全性の観点でextendsより落ちる。
ただし、AでもBでもない、全く別な型を作りたいという目的のために使う。
(extendsでも同じことはできるので、正直わかっていない)
code:ts
type X = {
value: number;
};
type Y = {
value: string;
};
type Z = X & Y;
const test: Z = {
value: "hello", // ❌ エラー: value は never 型
};
ユーティリティ型を使用する
型変換の繰り返しのコードが少なくて済む。
Partial
全てのキーをオプショナルに変換。
例) 元の型をオプショナルにしてやり、渡した値のキーのみ更新させることができる。
code:ts
type User = {
id: number;
name: string;
email: string;
};
function updateUser(user: User, updates: Partial<User>) {
return { ...user, ...updates };
}
const user: User = { id: 1, name: "Taro", email: "taro@example.com" };
const updatedUser = updateUser(user, { name: "Jiro" }); // 部分的に更新
Pick<T, K>
指定したキーだけの型オブジェクトに変換。
例)
code:ts
type UserPreview = Pick<User, "id" | "name">;
const userPreview: UserPreview = { id: 1, name: "Taro" };
Omit<T, K>
指定したキーを削除して新たなオブジェクトに変換。
code:ts
type User = {
surname: string;
middleName?: string;
givenName: string;
age: number;
address?: string;
nationality: string;
createdAt: string;
updatedAt: string;
};
type Optional = "age" | "address" | "nationality" | "createdAt" | "updatedAt";
type Person = Omit<User, Optional>;
//type Person = {
// surname: string;
// middleName?: string | undefined;
// givenName: string;
//}
Readonly<T>
code:ts
const userConfig: Readonly<User> = {
id: 1,
name: "Taro",
email: "taro@example.com",
};
// userConfig.name = "Jiro"; // エラー: Readonly<User> は変更不可
Record<K, T>
指定した型のオブジェクト型を生成する。
例) Kがユニオン型でそれぞれをキーとした、Tがstring[]となるオブジェクト。
code:ts
type Role = "admin" | "user" | "guest";
const rolePermissions: Record<Role, string[]> = {
admin: "read", "write", "delete",
user: "read", "write",
guest: "read",
};
不変型を優先する
const アサーション、readonly 修飾子、ReadonlyArray<T> を使用する
code:ts
// const アサーション
const config = {
apiUrl: "https://example.com",
timeout: 5000,
} as const;
// config.apiUrl = "https://new-url.com"; // エラー
code:ts
// ReadonlyArray
const numbers: ReadonlyArray<number> = 1, 2, 3;
// numbers.push(4); // エラー
// numbers0 = 10; // エラー
console.log(numbers0); // OK
型ガードを使用する
typeof
型によって条件分岐をさせることができたりする。
code:ts
function numberToStirng(value: string | number) {
if (typeof value === "number") {
return value.toString();
}
return value;
}
is
特定の型であることを明示的に表せる
code:ts
function isString(value: unknown): value is string {
return typeof value === "string";
}
function printLength(value: unknown) {
if (isString(value)) {
console.log(value.length); // ✅ OK
} else {
console.log("文字列ではありません");
}
}
printLength("TypeScript"); // 10
printLength(123); // 文字列ではありません
上の例で場合で、unknownだとbooleanが戻り値の型とするとエラーとなる
code:ts
function isString(value: unknown): boolean {
return typeof value === "string";
}
function printLength(value: unknown) {
if (isString(value)) {
console.log(value.length); // ❌ エラー: Object is of type 'unknown'.
}
}
https://zenn.dev/oreo2990/articles/5f75eaa285f2f9
TSでテストを書く
テストを書く習慣がないのでskip